JWT入门以及常见的登录问题

您所在的位置:网站首页 jwt 认证怎么放入http头部 JWT入门以及常见的登录问题

JWT入门以及常见的登录问题

2023-06-11 05:13| 来源: 网络整理| 查看: 265

在这里插入图片描述

文章目录 1 JWT概念1.1 什么是JWT1.2 JWT有什么用1.3 JWT的组成结构1.4 JWT的优点和缺点 2 使用JWT2.1 引入jwt坐标2.2 生成token2.3 解析token2.4 jwt工具类 3 登录问题3.1 有无状态登录3.1.1 有状态登录3.1.2 无状态的登录 3.2 单体项目登录问题3.3 分布式项目登录问题3.3.1 解析token并向下游的微服务传递3.3.2 完整的登录和鉴权过程3.3.3 Token续签问题3.3.4 Token续期处理3.3.5 Token登出处理 3.4 如何获取客户端的各种信息

本文主要介绍了JWT是什么,如何使用JWT以及实际开发中可能会遇到的有关JWT登录问题,比如token的续签、续期和登出问题等。

1 JWT概念 1.1 什么是JWT

JWT官方网址

Json Web Token是通过数字签名的方式,以json为载体,在不同的服务之间安全的传输信息的技术。

1.2 JWT有什么用

一般使用在授权认证的过程中,一旦用户登录,后端返回一个token给前端,相当于后端给了前端返回了一个授权码,之后前端向后端发送的每一个请求都需要包含这个token,后端在执行方法前会校验这个token(安全校验),校验通过才执行具体的业务逻辑。

1.3 JWT的组成结构

JWT由Header(头信息),PayLoad (用户信息),Signature(签名)三个部分组成请添加图片描述

Header头信息主要声明加密算法:(具体算法对称不对称加密不作为研究内容)

通常直接使用 HMAC HS256这样的算法

{ "typ":"jwt" "alg":"HS256" //加密算法 }

然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9

PayLoad(载荷) 指定了七个默认字段供选择

iss:发行人 exp:到期时间 sub:主体 aud:用户 nbf:在此之前不可用 iat:发布时间 jti:JWT ID用于标识该JWT

也可以自定义私有字段,但是不建议在此存放敏感信息,因为此部分可以解码还原出原始内容。虽然它可以解码,但是也不能修改这个内容。

{ "username":"zhangsan", "name":"张三", }

对其进行base64加密,得到Jwt的第二部分

eyJ1c2VybmFtZSI6InpoYW5nc2FuIiwibmFtZSI6IuW8oOS4iSIsImFnZSI6MTgsInNleCI6IuWlsyIsImV4cCI6MTY0NzE0NTA1MSwianRpIjoiMTIxMjEyMTIxMiJ9

Signature 签证信息,此部分用于防止jwt内容被篡改。这个签证信息由三部分组成(由加密后的Header,加密后的PayLoad,加密后的签名三部分组成)

header (base64后的)payload (base64后的)secret

base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐加密,然后就构成了jwt的第三部分,每个部分直接使用"."来进行拼接

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6InpoYW5nc2FuIiwibmFtZSI6IuW8oOS4iSIsImFnZSI6MTgsInNleCI6IuWlsyIsImV4cCI6MTY0NzE0NTA1MSwianRpIjoiMTIxMjEyMTIxMiJ9.5tmHCpcsS_VuZ2_z5Rydf2OpsviBGwB-fJE5aS7gKqE

1.4 JWT的优点和缺点

基于session和基于jwt的方式的主要区别就是用户的状态保存的位置,session是保存在服务端的,而jwt是保存在客户端的。

优点:

jwt基于json,非常方便解析。

可以在令牌中自定义丰富的内容,易扩展。

资源服务使用JWT可不依赖认证服务即可完成授权。

通过非对称加密算法及数字签名技术,JWT防止篡改,安全性高。

缺点:

JWT 中的信息可以在客户端解码,因此敏感信息不应该存储在 JWT 中,尤其是不加密的情况下。

如果使用对称加密算法并且密钥被泄漏,攻击者可以使用该密钥签发有效的 JWT。为了防止这种情况发生,应该使用安全的加密算法,并妥善保管密钥。

JWT 不支持会话管理,也不能主动使令牌失效。因此,在某些情况下,需要实现其他机制来管理用户会话和授权状态。

JWT 一旦签发,就无法撤回或修改,除非到了过期时间。因此,如果令牌被盗用,攻击者可以使用它来获得未经授权的访问权限。

JWT 的长度相对较长,可能会影响网络传输性能。

2 使用JWT 2.1 引入jwt坐标 io.jsonwebtoken jjwt 0.9.1 2.2 生成token //1.声明加密的算法 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //2.声明过期时间 long expMillis = System.currentTimeMillis() + 过期毫秒值; Date exp = new Date(expMillis); //3.配置jwt中要存入的信息 Map claims = new HashMap(); claims.put("id",1001); claims.put("name","admin"); //4.创建密钥 String secretKey = "miyao" //5.生成jwt String token = Jwts.builder() //配置jwt中需要保存的数据 .setClaims(claims) //配置加密算法&加密的key .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8)) //配置过期时间 .setExpiration(exp) //根据配置信息生成jwt .compact(); 2.3 解析token Claims claims = Jwts.parser() // 设置签名的秘钥 .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8)) // 设置需要解析的jwt .parseClaimsJws(token).getBody(); int id = claims.get("id"); 2.4 jwt工具类 /** * jwt工具类 */ public class JwtUtils { /** * 生成token加密令牌 * @param params => 存入的数据 * @param secretKey => 签名的密钥 * @param ttlMills => 过期时间(毫秒) * @return 生成的jwt token */ public static String createToken(Map params, String secretKey, long ttlMills){ //1.声明加密的算法 SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //2.声明过期时间 long expMillis = System.currentTimeMillis() + ttlMills; Date exp = new Date(expMillis); //3.生成jwt String token = Jwts.builder() //配置jwt中需要保存的数据 .setClaims(params) //配置加密算法&加密的key .signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8)) //配置过期时间 .setExpiration(exp) //根据配置信息生成jwt .compact(); return token; } /** * 验证jwt, 解析token * @param token => 加密令牌 * @param secretKey => 密钥 * @return 解析的结果集 */ public static Claims parseToken(String token, String secretKey){ Claims claims = Jwts.parser() // 设置签名的秘钥 .setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8)) // 设置需要解析的jwt .parseClaimsJws(token).getBody(); return claims; } } 3 登录问题 3.1 有无状态登录 3.1.1 有状态登录

cookie+session,所谓的状态就是在服务端存储session信息。客户端访问服务端的时候,在cookie中携带sessionId,服务端根据sessionId就能才找到对应的session信息。

缺点:分布式支持不好,默认是内存存储,一般用的时候:把sessionId存到redis中

3.1.2 无状态的登录

服务端不存储session信息,就需要token机制,客户端访问服务端的时候,需要在header或者是cookie中传递token信息,并且token中是携带了用户信息的。一旦生成,不能修改。

优点:分布式支持好

缺点:实现比较麻烦,续约、登出

续约:每次访问重新生成一个新的token(不推荐);使用两个token,accesstoken refreshtoken(有效期长); 临近过期生成新的token

登出: 登出之后,把token的id存入redis的黑名单

3.2 单体项目登录问题

单体项目,可以使用session来维持会话,但是生产环境为了避免单点故障,一般都会部署多个节点,为了避免session不同步的问题,可以在nginx上开启ip_hash这种负载均衡算法,让来自同一个ip的客户端请求始终落到同一个tomcat上,就是所谓的session粘连。

ip_hash

nginx提供ip_hash指令可实现session粘连需求,根据用户ip计算hash值,从而每次都分配到指定机器上,ip_hash支持配置weight权重。

但是ip_hash也有个不足,要求nginx必须是最前端的机器,即用户不经过其他服务中转,直接请求nginx,这样ip_hash才能生效。如果在nginx和用户中间有别的服务,比如SLB、LVS等负载集群,nginx的ip_hash指令可能会失效。

Nginx配置如下:

worker_processes 1; events { worker_connections 1024; } http { upstream ipHashDemo{ ip_hash; server 127.0.0.1:8081; server 127.0.0.1:8082; } server{ listen 8888; server_name 127.0.0.1; location / { proxy_pass http://ipHashDemo; } } }

worker_processes 1; 表示 Nginx 使用单个 worker 进程。

events { worker_connections 1024; } 表示每个 worker 进程可以同时处理的最大连接数为 1024。

upstream ipHashDemo{…} 定义了一个名为 “ipHashDemo” 的负载均衡组,使用 IP 哈希算法,并将请求转发到两台后端服务器(分别监听 8081 和 8082 端口)。

server {…} 定义了一个虚拟主机,监听在本地 8888 端口,请求经过负载均衡组 “ipHashDemo” 处理并代理到后端服务器。

location / {…} 表示该虚拟主机对所有请求都进行代理。

根据上面的配置文件会启动一个 Nginx 实例,在监听 8888 端口时对所有请求进行负载均衡,ip_hash在上面的配置文件中用于定义一个名为 “ipHashDemo” 的负载均衡组,并使用 IP 哈希算法对请求进行路由。这样做的好处是在多个请求中,相同 IP 的请求总是会被路由到同一台后端服务器上,避免了一些请求需要重复地建立连接和发送状态信息的情况,提高了性能。

upstream hash

既然自带的ip_hash指令不好使,那么能不能自己指定一个字段来计算hash值,替代ip值,比如cookie或者request uri里面的一个能标识用户id的字段。

有个第三方的nignx模块nginx_upstream_hash-0.3.1可以实现该效果,该模块提供hash指令,可以指定一个变量用于hash,示例如下:

hash指令后面的变量可以设置为其他有效的nginx变量。

http { upstream backend { hash $arg_user_id consistent; server backend1.example.com; server backend2.example.com; server backend3.example.com; } server { listen 80; location / { proxy_pass http://backend; } } }

在这个配置中,我们定义了一个名为 “backend” 的负载均衡后端,指定了哈希指令 “hash $arg_user_id consistent”,即使用 GET 请求参数中的 “user_id” 变量作为哈希键,并且启用一致性哈希算法。然后我们将三个后端服务器添加到 upstream 中。

最后在 server 块中,我们将请求转发到 “backend” 负载均衡组。由于我们在 upstream 中启用了哈希算法并指定了要使用的变量,因此同一用户的请求会被分配到同一台后端服务器上。

3.3 分布式项目登录问题

redis存储session信息

用户登录成功以后,服务端生成一个uuid(token),同时把uuid作为key,用户信息作为value存储到redis中,然后把uuid返回给客户端。客户端在访问服务端接口的时候,可以在cookie或者header中传递这个uuid,服务端收到请求,首先读取这个uuid,然后根据uuid去redis中查找用户。这种方式也可以避免session的不同步问题,因为现在是存储到了多个节点都可以访问的redis中。

3.3.1 解析token并向下游的微服务传递

以admin为例,admin中所有的接口(除了登录),都需要登陆以后才能访问。admin服务接口中需要知道是哪个用户的请求。所以网关直接把解析出来的userId传递给下游的微服务即可。

如何传递?

// 把userId传递给下游的微服务 request.mutate().header("userId", "" + userId);

使用的是spring5提供webflux的api向request中追加请求头。

3.3.2 完整的登录和鉴权过程

1)前端用户传递的用户名和密码,服务端收到请求,根据用户名到db查询记录,根据db中记录的password与前端传递的password做匹配,如果能匹配成功,则生成jwt token返回给前端,token中携带了userId。

2)前端会把token保存到local storage(本地存储器)中,在随后的访问中,在header中携带上这个token。

3)前端的请求首先是到网关,网关中有一个全局的过滤器,会从请求中读取header中的token,解析,把解析出来的userId放入http的header中继续向下游的微服务传递,header的key=userId。

4)微服务中有一个拦截器,在拦截器中拦截请求,从header中解析出userId,然后保存到ThreadLocal中。

5)在微服务的接口中,就可以使用ThreadLocal来获取用户信息。

3.3.3 Token续签问题

单token方案

将 token 过期时间设置为15分钟;前端发起请求,后端验证 token 是否过期;如果过期,前端发起刷新token请求,后端为前端返回一个新的token;前端用新的token发起请求,请求成功;如果要实现每隔72小时,必须重新登录,后端需要记录每次用户的登录时间;用户每次请求时,检查用户最后一次登录日期,如超过72小时,则拒绝刷新token的请求,请求失败,跳转到登录页面。

另外后端还可以记录刷新token的次数,比如最多刷新50次,如果达到50次,则不再允许刷新,需要用户重新授权。

双token方案

登录成功以后,后端返回 access_token 和 refresh_token,客户端缓存此两种token;使用 access_token 请求接口资源,成功则调用成功;如果token超时,客户端携带 refresh_token 调用token刷新接口获取新的 access_token;后端接受刷新token的请求后,检查 refresh_token 是否过期。如果过期,拒绝刷新,客户端收到该状态后,跳转到登录页;如果未过期,生成新的 access_token 返回给客户端。客户端携带新的 access_token 重新调用上面的资源接口。客户端退出登录或修改密码后,注销旧的token,使 access_token 和 refresh_token 失效,同时清空客户端的 access_token 和 refresh_toke。

1, 登录成功,生成两个token存到cookie或者local storage (一长一短)

2, 发起请求,网关检验token(短),验证通过则添加到请求头并放行路由到微服务,不通过则说明过期或者被篡改, 封装响应码通知前端

3, 前端接收通过,将token(长)发送给后端请求刷新token的请求,后端检验token(长),验证通过则重新生成两个token返回给前端,然后添加到请求头并放行路由到微服务,不通过则说明过期或者被篡改

3.3.4 Token续期处理

用户登录完成以后,服务端会生成两个token,一个是有效期比较短的accesstoken,比如2小时,还有一个是有效期长的refreshToken,比如30天,客户端会把这两个token都保存到客户端本地存储 客户端在随后访问服务端接口的时候,需要在header中携带accesstoken。请求首先是到网关,网关会做token过期时间的判断,如果token没过期则正常放行到后端的微服务,如果token已经过期,网关会返回一个特殊的代表token过期的响应码4001,客户端收到4001状态码的响应以后,不会向客户端提示失败,而目继续访问服务端提供的refresh token的请求,这个请求会返回一个新的accesstoken和refreshtoken,客户端会继续使用这个新的accesstoken去访问服务端的接口。

3.3.5 Token登出处理

生成token的时候,会给token设置一个token id 当用户退出的时候,把token id存放到redis中,key: token id,value:userld,有效期设置2小时 网关收到请求的时候,首先是根据token id去redis中查询,如果能查到值,说明token已经退出了,则返回token已经失效,如果查不到,则说明token是有效的,继续后续的业务逻辑外理。

3.4 如何获取客户端的各种信息 HttpServletRequest request = ServletActionContext.getRequest(); System.out.println("浏览器基本信息:"+request.getHeader("user-agent")); System.out.println("客户端系统名称:"+System.getProperty("os.name")); System.out.println("客户端系统版本:"+System.getProperty("os.version")); System.out.println("客户端操作系统位数:"+System.getProperty("os.arch")); System.out.println("HTTP协议版本:"+request.getProtocol()); System.out.println("请求编码格式:"+request.getCharacterEncoding()); System.out.println("Accept:"+request.getHeader("Accept")); System.out.println("Accept-语言:"+request.getHeader("Accept-Language")); System.out.println("Accept-编码:"+request.getHeader("Accept-Encoding")); System.out.println("Connection:"+request.getHeader("Connection")); System.out.println("Cookie:"+request.getHeader("Cookie")); System.out.println("客户端发出请求时的完整URL"+request.getRequestURL()); System.out.println("请求行中的资源名部分"+request.getRequestURI()); System.out.println("请求行中的参数部分"+request.getRemoteAddr()); System.out.println("客户机所使用的网络端口号"+request.getRemotePort()); System.out.println("WEB服务器的IP地址"+request.getLocalAddr()); System.out.println("WEB服务器的主机名"+request.getLocalName()); System.out.println("客户机请求方式"+request.getMethod()); System.out.println("请求的文件的路径"+request.getServerName()); System.out.println("请求体的数据流"+request.getReader()); BufferedReader br=request.getReader(); String res = ""; while ((res = br.readLine()) != null) { System.out.println("request body:" + res); } System.out.println("请求所使用的协议名称"+request.getProtocol()); System.out.println("请求中所有参数的名字"+request.getParameterNames()); Enumeration enumNames= request.getParameterNames(); while (enumNames.hasMoreElements()) { String key = (String) enumNames.nextElement(); System.out.println("参数名称:"+key); }

在这里插入图片描述



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3